/*
 * MIT License
 *
 * Copyright (c) 2019-2021 JetBrains s.r.o.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.jetbrains.projector.client.common

import org.jetbrains.projector.client.common.Renderer.Companion.RequestedRenderingState
import org.jetbrains.projector.client.common.canvas.Canvas
import org.jetbrains.projector.client.common.canvas.buffering.RenderingSurface
import org.jetbrains.projector.client.common.misc.ImageCacher
import org.jetbrains.projector.client.common.misc.ParamsProvider
import org.jetbrains.projector.common.misc.Do
import org.jetbrains.projector.common.protocol.data.ImageEventInfo
import org.jetbrains.projector.common.protocol.data.PaintValue
import org.jetbrains.projector.common.protocol.toClient.*
import org.jetbrains.projector.util.logging.Logger

/**
 * TODO 芋艿：渲染处理器？？？
 * 作用：将 DrawEvent 转化到对应的 Render 的方法中。例如说，
 *  ServerSetCompositeEvent 对应 Render 的 setComposite 方法
 *  ServerSetClipEvent 对应 Render 的 setClip 方法
 *
 * Single：单一，只处理对应的 Window 的 Render，不像 ServerEventsProcessor 对应的实际是多个 Window
 * Rendering：渲染
 * Surface：TODO 芋艿：怪怪的，难道 windows Surface 曲面？？啊哈哈
 */
class SingleRenderingSurfaceProcessor(renderingSurface: RenderingSurface, private val imageCacher: ImageCacher) {

  private val renderer = Renderer(renderingSurface)

  private val stateSaver = StateSaver(renderer, renderingSurface)

  /**
   * 处理 pending 绘制事件
   */
  fun processPending(drawEvents: ArrayDeque<DrawEvent>) {
    // 恢复状态，@see processNew 方法
    stateSaver.restoreIfNeeded()

    // 绘制 pending 的事件
    val firstUnsuccessful = processNew(drawEvents)
    // 情况一，都绘制成功，则清空事件
    if (firstUnsuccessful == null) {
      drawEvents.clear()
    }
    // 情况二，存在绘制失败的，则移除前面已经成功的
    else {
      repeat(firstUnsuccessful) {
        drawEvents.removeFirst()
      }
    }
  }

  /**
   * 处理 new 绘图事件
   */
  fun processNew(drawEvents: Iterable<DrawEvent>): Int? {
    // 第一个绘图失败的位置
    var firstUnsuccessful: Int? = null

    // 顺序绘图
    drawEvents.forEachIndexed { index, drawEvent ->
      // 执行绘图
      val drawIsSuccessful = handleDrawEvent(drawEvent)
      // 如果绘图失败，记录首个失败到 firstUnsuccessful
      if (!drawIsSuccessful && firstUnsuccessful == null) {
        // 记录状态，@see processPending 方法
        stateSaver.saveIfNeeded()
        firstUnsuccessful = index
      }
    }

    // 返回第一个绘图失败的位置
    return firstUnsuccessful
  }

  /**
   * 【重要】真正处理 DrawEvent 事件，使用 Render 进行绘制
   * @param command 绘制事件
   * @return 绘制是否成功。
   *    ps：目前，只有图片会绘制失败，因为本地没有该图片！！！
   */
  private fun handleDrawEvent(command: DrawEvent): Boolean {
    // ① 遍历 ServerWindowStateEvent 事件，设置到 Render 中
    command.prerequisites.forEach {
      Do exhaustive when (it) {
        is ServerSetCompositeEvent -> renderer.setComposite(it.composite)

        is ServerSetPaintEvent -> it.paint.let { paintValue ->
          when (paintValue) {
            is PaintValue.Color -> renderer.setColor(paintValue.argb)

            is PaintValue.Gradient -> renderer.setGradientPaint(
              p1 = paintValue.p1,
              p2 = paintValue.p2,
              color1 = paintValue.argb1,
              color2 = paintValue.argb2
            )

            is PaintValue.Unknown -> logUnsupportedCommand(it)
          }
        }

        is ServerSetClipEvent -> renderer.setClip(it.shape)

        is ServerSetFontEvent -> renderer.setFont(it.fontId, it.fontSize, it.ligaturesOn)

        is ServerSetStrokeEvent -> renderer.setStroke(it.strokeData)

        is ServerSetTransformEvent -> renderer.setTransform(it.tx)

        is ServerWindowToDoStateEvent -> logUnsupportedCommand(it)
      }
    }

    // ② 处理 ServerWindowPaintEvent 事件，真正的绘制，使用 Render
    var success = true
    with(command.paintEvent) {
      Do exhaustive when (this) {
        // 线
        is ServerDrawLineEvent -> renderer.drawLine(
          x1 = x1.toDouble(),
          y1 = y1.toDouble(),
          x2 = x2.toDouble(),
          y2 = y2.toDouble()
        )

        // 字符串
        is ServerDrawStringEvent -> renderer.drawString(
          string = str,
          x = x,
          y = y,
          desiredWidth = desiredWidth
        )

        // 矩形
        is ServerPaintRectEvent -> renderer.paintRect(
          paintType = paintType,
          x = x,
          y = y,
          width = width,
          height = height
        )

        // 圆角矩形
        is ServerPaintRoundRectEvent -> renderer.paintRoundRect(
          paintType = paintType,
          x = x.toDouble(),
          y = y.toDouble(),
          w = width.toDouble(),
          h = height.toDouble(),
          r1 = arcWidth.toDouble(),
          r2 = arcHeight.toDouble()
        )

        // 图片
        is ServerDrawImageEvent -> {
          // 从缓存中获得图片
          val image = imageCacher.getImageData(imageId)
          // 获得不到，标记绘制失败
          if (image == null) {
            success = false
          }
          else {
            val info = imageEventInfo
            // 绘制图片
            Do exhaustive when (info) {
              is ImageEventInfo.Ds -> {
                // todo: manage bgcolor
                val dw = info.dx2 - info.dx1
                val dh = info.dy2 - info.dy1
                val sw = info.sx2 - info.sx1
                val sh = info.sy2 - info.sy1

                renderer.drawImage(
                  image = image,
                  dx = info.dx1.toDouble(),
                  dy = info.dy1.toDouble(),
                  dw = dw.toDouble(),
                  dh = dh.toDouble(),
                  sx = info.sx1.toDouble(),
                  sy = info.sy1.toDouble(),
                  sw = sw.toDouble(),
                  sh = sh.toDouble()
                )
              }

              is ImageEventInfo.Xy -> {
                // todo: manage bgcolor
                renderer.drawImage(image, info.x.toDouble(), info.y.toDouble())
              }

              is ImageEventInfo.XyWh -> {
                // todo: manage bgcolor
                renderer.drawImage(
                  image = image,
                  x = info.x.toDouble(),
                  y = info.y.toDouble(),
                  width = info.width.toDouble(),
                  height = info.height.toDouble()
                )
              }

              is ImageEventInfo.Transformed -> renderer.drawImage(image, info.tx)
            }
          }
        }

        is ServerPaintPathEvent -> renderer.paintPath(paintType, path)

        is ServerPaintOvalEvent -> renderer.paintOval(
          paintType = paintType,
          x = x.toDouble(),
          y = y.toDouble(),
          width = width.toDouble(),
          height = height.toDouble()
        )

        is ServerPaintPolygonEvent -> renderer.paintPolygon(paintType, points)

        is ServerDrawPolylineEvent -> renderer.drawPolyline(points)

        is ServerCopyAreaEvent -> renderer.copyArea(
          x = x.toDouble(),
          y = y.toDouble(),
          width = width.toDouble(),
          height = height.toDouble(),
          dx = dx.toDouble(),
          dy = dy.toDouble()
        )

        is ServerWindowToDoPaintEvent -> logUnsupportedCommand(this)
      }
    }

    return success
  }

  /**
   * 状态服务器
   * 作用：用于 draw 失败时，记录当前的状态。以便，后续重试 draw 时，可以恢复到该状态
   */
  private class StateSaver(private val renderer: Renderer, private val renderingSurface: RenderingSurface) {

    /**
     * 最后成功时的状态
     */
    private var lastSuccessfulState: LastSuccessfulState? = null

    /**
     * 记录状态
     * 例如说，将当前界面的 canvas 转换成图片存储
     */
    fun saveIfNeeded() {
      if (lastSuccessfulState != null) {
        return
      }

      lastSuccessfulState = LastSuccessfulState(
        renderingState = renderer.requestedState.copy(),
        image = renderingSurface.canvas.takeSnapshot()
      )
    }

    /**
     * 恢复状态
     * 例如说，将记录的图片绘制到 canvas
     */
    fun restoreIfNeeded() {
      lastSuccessfulState?.let {
        renderer.drawImageRaw(it.image)
        renderer.requestedState.setTo(it.renderingState)

        lastSuccessfulState = null
      }
    }

    private class LastSuccessfulState(val renderingState: RequestedRenderingState, val image: Canvas.Snapshot)
  }

  companion object {

    private val logger = Logger<SingleRenderingSurfaceProcessor>()

    private fun logUnsupportedCommand(command: ServerWindowEvent) {
      if (ParamsProvider.LOG_UNSUPPORTED_EVENTS) {
        logger.debug { "Unsupported: $command" }
      }
    }

    /**
     * 获得所有 ServerWindowPaintEvent 事件的数组，排除掉里面的 ServerWindowStateEvent 事件
     */
    fun List<ServerWindowEvent>.shrinkByPaintEvents(): List<DrawEvent> {
      val result = mutableListOf<DrawEvent>()

      var prerequisites = mutableListOf<ServerWindowStateEvent>()

      this.forEach {
        Do exhaustive when (it) {
          is ServerWindowStateEvent -> prerequisites.add(it)

          is ServerWindowPaintEvent -> {
            result.add(DrawEvent(prerequisites, it))
            prerequisites = mutableListOf()
          }
        }
      }

      // TODO 芋艿：为啥会出现 ServerWindowStateEvent 的情况？
      if (prerequisites.isNotEmpty()) {
        logger.error { "Bad commands received from server: ${prerequisites.size} state events are at the end" }
      }

      return result
    }
  }
}
